#! /usr/bin/env python
#coding=utf-8

import os
from stat import *

from utils import command
from symbols import Symbol
from symbols import ProvidedSymbol

# https://adrummond.net/posts/macho

class MachOFileBase(dict):
	def __init__(self, file, prefix):
		self._f = file
		self._f_safe = "'%s'" % file

		self["name"] = file[len(prefix):]
		self["size"] = os.stat(self._f)[ST_SIZE]
		#self.extract_elf_size()

	def __eq__(self, other):
		if not isinstance(other, MachOFileBase):
			return NotImplemented

		return self["name"] == other["name"]

	def extract_elf_size(self):
		size_data = command("size", self._f_safe)
		if not size_data or len(size_data) < 2:
			self["text_size"] = 0
			self["data_size"] = 0
			self["oc_size"] = 0
			self["others_size"] = 0
			return 0

		vals = size_data[1].split()
		self["text_size"] = int(vals[0])
		self["data_size"] = int(vals[1])
		self["oc_size"] = int(vals[2])
		self["others_size"] = int(vals[3])
		return ""

	def is_library(self):
		if self["name"].find(".dylib") > 0:
			return True
		return False

	def get_file(self):
		return self._f

	# Return a set of symbols provided by a library
	# Refer to https://www.man7.org/linux/man-pages/man1/objdump.1.html
	# addr, flag bits, section name, symbol name
	#   l: (local), g: (global)
	#   !: (both global and local, indication of bug)
	#   u: (unique global, GNU extension, only one symbol with this name)
	#   w: (weak symbol)
	#   C: (constructor)
	#   W: (warning)
	#   I,i: (indirect reference to another symbol)
	#   d,D: (debugging symbol)

	#   F,f,O: (the name of a function, file or object)
	def symbols(self, logFile=None):
		if not os.access(self._f, os.F_OK):
			raise Exception("Cannot find lib" + self._f)

		output = command("objdump", "--macho", "--syms", self._f_safe)
		if logFile:
			logFile.write("got %d output\n" % len(output))

		provided = []
		undefined = []
		firstLine = True
		for line in output:
			line = line.strip()
			# Ignore heading lines
			if firstLine or line == "" or line == "SYMBOL TABLE:":
				firstLine = False
				continue

			parts = line.split()

			try:
				addr = int(parts[0], 16)
			except:
				print(parts[0])
				continue

			# Undefined symbols has addr == 0
			bits = parts[1]
			if addr == 0 or len(parts) < 4:
				bits = ""
				if (len(parts)) > 3:
					bits = parts[1]
					name = parts[3]
				else:
					name = parts[2]

				undefined.append(Symbol(name, self, bits))
			else:
				name_type = parts[2]
				section = parts[3]
				name = " ".join(parts[4:])

				provided.append(ProvidedSymbol(name, self, bits, parts[0], section, name_type))

		return provided, undefined

	def provided_symbols(self):
		if not os.access(self._f, os.F_OK):
			raise Exception("Cannot find lib" + self._f)
		library = self["name"]

		output = command("objdump", "--macho", "--syms", self._f_safe)

		result = []
		firstLine = True
		for line in output:
			line = line.strip()
			# Ignore heading lines
			if firstLine or line == "" or line == "SYMBOL TABLE:":
				firstLine = False
				continue

			parts = line.split()

			try:
				addr = int(parts[0], 16)
			except:
				print(parts[0])
				continue

			# Undefined symbols has addr == 0
			if addr == 0 or len(parts) < 4:
				continue
			addr = parts[0]
			bits = parts[1]
			name_type = parts[2]
			section = parts[3]
			name = " ".join(parts[4:])

			result.append(ProvidedSymbol(name, self, bits, addr, section, name_type))

		return result

	# Return undefined symbols in an object as a set of tuples (name, bits)
	def undefined_symbols(self):
		if not os.access(self._f, os.F_OK):
			raise Exception("Cannot find lib" + self._f)
		library = self["name"]

		output = command("objdump", "--macho", "--syms", self._f_safe)

		result = []
		firstLine = True
		for line in output:
			line = line.strip()
			# Ignore heading lines
			if firstLine or line == "" or line == "SYMBOL TABLE:":
				firstLine = False
				continue

			parts = line.split()

			try:
				addr = int(parts[0])
			except:
				continue

			# Undefined symbols has addr == 0 or 3 parts
			if addr != 0 and len(parts) > 4:
				continue

			addr = parts[0]
			bits = ""
			if (len(parts)) > 3:
				bits = parts[1]
				name = parts[3]
			else:
				name = parts[2]

			result.append(Symbol(name, self, bits))

		return result

	# Return a set of libraries the passed objects depend on.
	def library_depends(self):
		if not os.access(self._f, os.F_OK):
			raise Exception("Cannot find lib: " + self._f)
		output = command("objdump", "--macho", "--dylibs-used", self._f_safe)
		result = []
		firstLine = True
		for line in output:
			line = line.strip()
			# Ignore heading lines
			if firstLine or line == "":
				firstLine = False
				continue

			line = line[:line.find(" ")]
			line = line.strip()
			if line == self["name"]:
				continue
			result.append(line)

		return result

if __name__ == '__main__':
	import macho_walker

	machoFiles = macho_walker.MachOWalker()
	for f in machoFiles.get_all_files():
		if f.find("/System/iOSSupport/System/Library/PrivateFrameworks/NewsEngagementCollector.framework/Versions/A/NewsEngagementCollector") < 0:
			continue
		dylib = MachOFileBase(f, machoFiles.get_path())
		print(f)
		#print(dylib.library_depends())
		provided, undefined = dylib.symbols()
		print(len(provided))
		print(len(undefined))
